#include "userprog/process.h"
#include <debug.h>
#include <inttypes.h>
#include <round.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "userprog/gdt.h"
#include "userprog/pagedir.h"
#include "userprog/tss.h"
#include "filesys/directory.h"
#include "filesys/file.h"
#include "filesys/filesys.h"
#include "threads/flags.h"
#include "threads/init.h"
#include "threads/interrupt.h"
#include "threads/palloc.h"
#include "threads/thread.h"
#include "threads/vaddr.h"

static thread_func start_process NO_RETURN;
static bool load(const char *cmdline, void (**eip)(void), void **esp);

tid_t process_execute(const char *cmd_line) {
    char *fn_copy;
    char *save_ptr;
    char *program_name;

    /* Make a copy of CMD_LINE. */
    fn_copy = palloc_get_page(0);
    if (fn_copy == NULL)
        return TID_ERROR;
    strlcpy(fn_copy, cmd_line, PGSIZE);

    /* Extract program name. */
    program_name = strtok_r(fn_copy, " ", &save_ptr);

    /* Create a new thread to execute the program. */
    tid_t tid = thread_create(program_name, PRI_DEFAULT, start_process, fn_copy);
    if (tid == TID_ERROR)
        palloc_free_page(fn_copy);

    return tid;
}

static void start_process(void *cmd_line_) {
    char *cmd_line = cmd_line_;
    struct intr_frame if_;
    bool success;

    /* Parse command line. */
    char *save_ptr;
    char *argv[128];
    int argc = 0;

    for (char *token = strtok_r(cmd_line, " ", &save_ptr);
         token != NULL;
         token = strtok_r(NULL, " ", &save_ptr)) {
        argv[argc++] = token;
    }

    memset(&if_, 0, sizeof if_);
    if_.gs = if_.fs = if_.es = if_.ds = if_.ss = SEL_UDSEG;
    if_.cs = SEL_UCSEG;
    if_.eflags = FLAG_IF | FLAG_MBS;
    success = load(argv[0], &if_.eip, &if_.esp);

    if (success) {
        push_arguments(&if_.esp, argv, argc);
        asm volatile("movl %0, %%esp; jmp intr_exit" : : "g"(&if_) : "memory");
    }

    palloc_free_page(cmd_line);
    thread_exit();
}

static void push_arguments(void **esp, char *argv[], int argc) {
    char *arg_addresses[argc];
    int i;

    /* Push arguments onto stack in reverse order. */
    for (i = argc - 1; i >= 0; i--) {
        *esp -= strlen(argv[i]) + 1;
        memcpy(*esp, argv[i], strlen(argv[i]) + 1);
        arg_addresses[i] = *esp;
    }

    /* Align stack to multiple of 4. */
    uintptr_t alignment = (uintptr_t)(*esp) % 4;
    if (alignment != 0) {
        *esp -= alignment;
    }

    /* Push addresses of arguments. */
    for (i = argc - 1; i >= 0; i--) {
        *esp -= sizeof(char *);
        memcpy(*esp, &arg_addresses[i], sizeof(char *));
    }

    /* Push argv address. */
    char **argv_addr = *esp;
    *esp -= sizeof(char **);
    memcpy(*esp, &argv_addr, sizeof(char **));

    /* Push argc. */
    *esp -= sizeof(int);
    memcpy(*esp, &argc, sizeof(int));

    /* Push fake return address. */
    *esp -= sizeof(void *);
    memset(*esp, 0, sizeof(void *));
}

int process_wait(tid_t child_tid UNUSED) {
    return -1;
}

void process_exit(void) {
    struct thread *cur = thread_current();

    if (cur->pagedir) {
        printf("%s: exit(%d)\n", cur->name, cur->exit_status);
    }

    uint32_t *pd = cur->pagedir;
    if (pd != NULL) {
        cur->pagedir = NULL;
        pagedir_activate(NULL);
        pagedir_destroy(pd);
    }
}

void process_activate(void) {
    struct thread *t = thread_current();

    pagedir_activate(t->pagedir);
    tss_update();
}
